namespace GomuLibrary.IO
{
    using GomuLibrary.IO.DiscImage;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading;

    public abstract class Iso9660Reader : IDisposable
    {
        private AutoResetEvent _autoEvent = new AutoResetEvent(false);
        private BinaryReader _baseBinaryReader;
        private FileStream _baseFileStream;
        protected Dictionary<string, RecordEntryInfo> _basicFilesInfo = new Dictionary<string, RecordEntryInfo>();
        private int _dataBeginSector = 0;
        private string _discFile = string.Empty;
        private List<string> _fsEntries = new List<string>();
        private int _sectorSize = 0x800;
        protected Dictionary<string, PathTableRecordPub> _tableRecords = new Dictionary<string, PathTableRecordPub>();
        private bool disposed;
        public const int ISO_SECTOR_SIZE = 0x800;
        private VolumeDescriptor primVolDesc;
        private object thisLock = new object();

        public event EventHandler<Iso9660FileExtractEventArgs> Aborted;

        public event EventHandler<Iso9660FileExtractEventArgs> Reading;

        private byte[] CleanUserDataBuffer(byte[] source, uint dataLength)
        {
            byte[] dst = new byte[dataLength];
            int num = 0;
            for (int i = 0; i < dataLength; i += 0x800)
            {
                if ((dataLength - i) >= 0x800L)
                {
                    Buffer.BlockCopy(source, (num * this._sectorSize) + this._dataBeginSector, dst, num * 0x800, 0x800);
                    num++;
                }
                else
                {
                    Buffer.BlockCopy(source, (this._sectorSize * num) + this._dataBeginSector, dst, num * 0x800, ((int) dataLength) - i);
                }
            }
            return dst;
        }

        protected void CloseDiscImageFile()
        {
            if (this._baseBinaryReader != null)
            {
                this._baseBinaryReader.Close();
            }
            if (this._baseFileStream != null)
            {
                this._baseFileStream.Close();
                this._baseFileStream.Dispose();
            }
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void Dispose(bool disposing)
        {
            if (!this.disposed && disposing)
            {
                this.CloseDiscImageFile();
                this._autoEvent.Close();
            }
            this.disposed = true;
        }

        private uint GetDataLengthDirectoryEntry(string path, ref uint lba)
        {
            string directoryName = Path.GetDirectoryName(path);
            PathTableRecordPub pub = (path == @"\") ? this._tableRecords[@"\"] : this._tableRecords[directoryName];
            PathTableRecordPub pub2 = this._tableRecords[path];
            lba = pub2.ExtentLocation;
            int num = Marshal.SizeOf(typeof(DirectoryRecord));
            DirectoryRecord structure = new DirectoryRecord();
            byte[] buffer = new byte[num];
            this._baseBinaryReader.BaseStream.Seek(pub.ExtentLocation * this._sectorSize, SeekOrigin.Begin);
            this._baseBinaryReader.BaseStream.Seek((long) this._dataBeginSector, SeekOrigin.Current);
            while (true)
            {
                this._baseBinaryReader.Read(buffer, 0, buffer.Length);
                GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
                structure = (DirectoryRecord) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(DirectoryRecord));
                handle.Free();
                if (structure.ExtentLocation > 0)
                {
                    if (structure.ExtentLocation == pub2.ExtentLocation)
                    {
                        return structure.DataLength;
                    }
                    this._baseBinaryReader.BaseStream.Seek((long) (structure.Length - Marshal.SizeOf(structure)), SeekOrigin.Current);
                }
                else
                {
                    this._baseBinaryReader.BaseStream.Seek(pub2.ExtentLocation * this._sectorSize, SeekOrigin.Begin);
                    this._baseBinaryReader.BaseStream.Seek((long) this._dataBeginSector, SeekOrigin.Current);
                    buffer = new byte[num];
                    this._baseBinaryReader.Read(buffer, 0, buffer.Length);
                    GCHandle handle2 = GCHandle.Alloc(buffer, GCHandleType.Pinned);
                    structure = (DirectoryRecord) Marshal.PtrToStructure(handle2.AddrOfPinnedObject(), typeof(DirectoryRecord));
                    handle2.Free();
                    return structure.DataLength;
                }
            }
        }

        protected virtual List<string> GetFileSystemEntries(string path, uint lba, uint length, bool recursive, bool first)
        {
            int index = 0;
            uint num2 = 0;
            uint size = 0;
            bool directory = false;
            bool hidden = false;
            int count = 0;
            string str = "";
            byte[] date = new byte[0];
            List<RecordEntryInfo> list = new List<RecordEntryInfo>();
            if (first)
            {
                this._fsEntries.Clear();
                this._basicFilesInfo.Clear();
            }
            try
            {
                uint dataLengthDirectoryEntry = this.GetDataLengthDirectoryEntry(path, ref num2);
                byte[] source = new byte[0];
                if (this._sectorSize == 0x800)
                {
                    source = this.ReadSectors(num2, dataLengthDirectoryEntry);
                }
                else
                {
                    source = this.ReadSectors(num2, Convert.ToUInt32((long) ((dataLengthDirectoryEntry / 0x800) * this._sectorSize)));
                    source = this.CleanUserDataBuffer(source, dataLengthDirectoryEntry);
                }
                while (index > -1)
                {
                    int num6 = source[index];
                    if (num6 > 0)
                    {
                        byte[] dst = new byte[4];
                        Buffer.BlockCopy(source, index + 2, dst, 0, 4);
                        num2 = BitConverter.ToUInt32(dst, 0);
                        dst = new byte[4];
                        Buffer.BlockCopy(source, index + 10, dst, 0, 4);
                        size = BitConverter.ToUInt32(dst, 0);
                        dst = new byte[7];
                        Buffer.BlockCopy(source, index + 0x12, dst, 0, 7);
                        date = dst;
                        int num7 = source[index + 0x19];
                        directory = Convert.ToBoolean((int) (num7 & 2));
                        hidden = Convert.ToBoolean((int) (num7 & 1));
                        count = source[index + 0x20];
                        if (this.primVolDesc.Type == 1)
                        {
                            str = Encoding.Default.GetString(source, index + 0x21, count);
                        }
                        else if (this.primVolDesc.Type == 2)
                        {
                            if ((count % 2) == 0)
                            {
                                str = Encoding.BigEndianUnicode.GetString(source, index + 0x21, count);
                            }
                            else
                            {
                                str = Encoding.Default.GetString(source, index + 0x21, count);
                            }
                        }
                        if (!string.IsNullOrEmpty(str) && (Encoding.Default.GetBytes(str.ToCharArray(), 0, 1)[0] > 1))
                        {
                            string item = Path.Combine(path, str);
                            item = item.EndsWith(";1") ? item.Remove(item.Length - 2) : item;
                            this._fsEntries.Add(item);
                            this._basicFilesInfo.Add(item, new RecordEntryInfo(num2, size, date, str, item, hidden, directory));
                            if (recursive && directory)
                            {
                                list.Add(new RecordEntryInfo(num2, size, date, str, item, hidden, directory));
                            }
                        }
                    }
                    else
                    {
                        num6 = 1;
                    }
                    index += num6;
                    if (index >= dataLengthDirectoryEntry)
                    {
                        index = -1;
                    }
                }
                source = null;
                if (recursive)
                {
                    for (int i = 0; i < list.Count; i++)
                    {
                        RecordEntryInfo info = list[i];
                        string str3 = Path.Combine(path, info.Name);
                        this.GetFileSystemEntries(str3, info.Extent, info.Size, recursive, false);
                    }
                }
                return this._fsEntries;
            }
            catch (Exception)
            {
                return this._fsEntries;
            }
        }

        protected virtual string[] GetTable()
        {
            List<string> list = new List<string>();
            Dictionary<string, PathTableRecordPub> dictionary = new Dictionary<string, PathTableRecordPub>();
            int num = Marshal.SizeOf(typeof(PathTableRecord));
            int srcOffset = 0;
            this.BaseFileStream = new FileStream(this._discFile, FileMode.Open, FileAccess.Read, FileShare.Read);
            this.BaseBinaryReader = new BinaryReader(this.BaseFileStream);
            byte[] source = new byte[0];
            uint typeLPathTable = this.primVolDesc.TypeLPathTable;
            uint pathTableSize = this.primVolDesc.PathTableSize;
            if (this.SectorSize == 0x800)
            {
                source = this.ReadSectors(typeLPathTable, pathTableSize);
            }
            else
            {
                source = this.ReadSectors(typeLPathTable, Convert.ToUInt32((long) (((pathTableSize / 0x800) + 1) * this.SectorSize)));
                source = this.CleanUserDataBuffer(source, pathTableSize);
            }
            try
            {
                while (srcOffset > -1)
                {
                    byte[] dst = new byte[num];
                    Buffer.BlockCopy(source, srcOffset, dst, 0, dst.Length);
                    GCHandle handle = GCHandle.Alloc(dst, GCHandleType.Pinned);
                    PathTableRecord record = (PathTableRecord) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(PathTableRecord));
                    handle.Free();
                    PathTableRecordPub pub = (PathTableRecordPub) record;
                    byte[] buffer3 = new byte[record.Length];
                    Buffer.BlockCopy(source, srcOffset + 8, buffer3, 0, buffer3.Length);
                    if (this.primVolDesc.Type == 1)
                    {
                        pub.Name = Encoding.Default.GetString(buffer3);
                    }
                    else if (this.primVolDesc.Type == 2)
                    {
                        if ((pub.Number % 2) == 0)
                        {
                            pub.Name = Encoding.BigEndianUnicode.GetString(buffer3);
                        }
                        else
                        {
                            pub.Name = Encoding.Default.GetString(buffer3);
                        }
                    }
                    if (!string.IsNullOrEmpty(pub.Name))
                    {
                        if (!pub.Name.Equals("\0"))
                        {
                            string item = Path.Combine(list[record.ParentNumber - 1], pub.Name);
                            list.Add(item);
                            dictionary.Add(item, pub);
                        }
                        else
                        {
                            list.Add(@"\");
                            dictionary.Add(@"\", pub);
                        }
                    }
                    if (srcOffset >= pathTableSize)
                    {
                        goto Label_02B9;
                    }
                    srcOffset += record.Length + 8;
                    if ((pub.Number % 2) > 0)
                    {
                        srcOffset++;
                    }
                    if (pub.Number == 0)
                    {
                        srcOffset++;
                    }
                    if (pub.ParentNumber == 0)
                    {
                        srcOffset++;
                    }
                }
            }
            catch (Exception exception)
            {
                string message = exception.Message;
            }
        Label_02B9:
            this._tableRecords = dictionary;
            return list.ToArray();
        }

        protected void ReadFile(RecordEntryInfo recordFileInfo, string outputPath, DiscImageReadMode mode, ImageFileFormat? format, int? recordIndex)
        {
            lock (this.thisLock)
            {
                string path = (mode == DiscImageReadMode.EXTRACT_FILE) ? Path.Combine(outputPath, Path.GetFileName(recordFileInfo.Name)) : Path.Combine(outputPath, recordFileInfo.FullPath.Substring(1));
                uint start = (uint) (recordFileInfo.Extent * this._sectorSize);
                uint size = recordFileInfo.Size;
                uint dataLength = size;
                if (mode == DiscImageReadMode.EXTRACT_FILE)
                {
                    this._baseFileStream = new FileStream(this._discFile, FileMode.Open, FileAccess.Read, FileShare.Read, this._sectorSize * 0x10);
                    this._baseBinaryReader = new BinaryReader(this._baseFileStream);
                }
                FileStream output = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, 0x8000);
                BinaryWriter writer = new BinaryWriter(output);
                this._baseBinaryReader.BaseStream.Seek((long) start, SeekOrigin.Begin);
                byte[] buffer = new byte[0];
                do
                {
                    if ((mode == DiscImageReadMode.EXTRACT_FILE) && this._autoEvent.WaitOne(0, false))
                    {
                        if (this.Aborted != null)
                        {
                            this.Aborted(null, new Iso9660FileExtractEventArgs(recordFileInfo.FullPath, 0, 0, 0, path, 0));
                        }
                        break;
                    }
                    dataLength = size - ((uint) writer.BaseStream.Position);
                    if (dataLength > 0)
                    {
                        if (dataLength > 0x8000)
                        {
                            buffer = new byte[this._sectorSize * 0x10];
                            this._baseBinaryReader.Read(buffer, 0, buffer.Length);
                            if (this._sectorSize == 0x800)
                            {
                                writer.Write(buffer);
                            }
                            else
                            {
                                writer.Write(this.CleanUserDataBuffer(buffer, 0x8000));
                            }
                        }
                        else if (this._sectorSize == 0x800)
                        {
                            buffer = new byte[dataLength];
                            this._baseBinaryReader.Read(buffer, 0, buffer.Length);
                            writer.Write(buffer);
                        }
                        else
                        {
                            buffer = new byte[((dataLength / 0x800) + 1) * this._sectorSize];
                            this._baseBinaryReader.Read(buffer, 0, buffer.Length);
                            writer.Write(this.CleanUserDataBuffer(buffer, dataLength));
                        }
                        writer.Flush();
                        if (this.Reading != null)
                        {
                            Iso9660FileExtractEventArgs e = new Iso9660FileExtractEventArgs(recordFileInfo.FullPath, start, size, size - dataLength, path, recordIndex.Value + 1);
                            this.Reading(null, e);
                        }
                    }
                }
                while (dataLength > 0);
                writer.Close();
                output.Close();
                output.Dispose();
                File.SetLastWriteTimeUtc(path, recordFileInfo.GetDate());
                if (recordFileInfo.Hidden)
                {
                    File.SetAttributes(path, FileAttributes.Normal | FileAttributes.Hidden);
                }
            }
        }

        protected internal virtual byte[] ReadSectors(uint startOffset, uint dataLength)
        {
            this._baseBinaryReader.BaseStream.Seek(startOffset * this._sectorSize, SeekOrigin.Begin);
            byte[] buffer = new byte[dataLength];
            this._baseBinaryReader.Read(buffer, 0, (int) dataLength);
            return buffer;
        }

        protected virtual VolumeInfo ReadVolumeDescriptor(string discPath, ref uint rootDirLocation, ref uint rootDirRecordLength)
        {
            this._discFile = discPath;
            if (!File.Exists(discPath))
            {
                throw new IOException($"{this._discFile} file not found");
            }
            int num = Marshal.SizeOf(typeof(VolumeDescriptor));
            GCHandle handle = new GCHandle();
            byte[] buffer = new byte[this._sectorSize];
            byte[] dst = new byte[this._sectorSize];
            try
            {
                this._baseFileStream = new FileStream(discPath, FileMode.Open, FileAccess.Read, FileShare.Read);
                this._baseBinaryReader = new BinaryReader(this._baseFileStream);
                this._baseBinaryReader.BaseStream.Seek((long) ((0x10 * this._sectorSize) + this._dataBeginSector), SeekOrigin.Begin);
                while (buffer[0] != 0xff)
                {
                    this._baseBinaryReader.Read(buffer, 0, this._sectorSize);
                    if (buffer[0] != 0xff)
                    {
                        Buffer.BlockCopy(buffer, 0, dst, 0, this._sectorSize);
                    }
                }
                handle = GCHandle.Alloc(dst, GCHandleType.Pinned);
                IntPtr ptr = handle.AddrOfPinnedObject();
                this.primVolDesc = (VolumeDescriptor) Marshal.PtrToStructure(ptr, typeof(VolumeDescriptor));
                handle.Free();
                rootDirLocation = this.primVolDesc.RootDirectoryRecord.ExtentLocation;
                rootDirRecordLength = this.primVolDesc.RootDirectoryRecord.DataLength;
            }
            catch (IOException exception)
            {
                throw new IOException(exception.Message, exception);
            }
            catch (Exception)
            {
                throw new Exception("An error occured during reading the volume descriptor of the disc image file");
            }
            finally
            {
                if (handle.IsAllocated)
                {
                    handle.Free();
                }
                this.CloseDiscImageFile();
            }
            return (VolumeInfo) this.primVolDesc;
        }

        protected AutoResetEvent AutoThreadEvent
        {
            get => 
                this._autoEvent;
            set
            {
                this._autoEvent = value;
            }
        }

        protected BinaryReader BaseBinaryReader
        {
            get => 
                this._baseBinaryReader;
            set
            {
                this._baseBinaryReader = value;
            }
        }

        protected FileStream BaseFileStream
        {
            get => 
                this._baseFileStream;
            set
            {
                this._baseFileStream = value;
            }
        }

        protected int DataBeginSector
        {
            get => 
                this._dataBeginSector;
            set
            {
                this._dataBeginSector = value;
            }
        }

        protected string DiscFilename =>
            this._discFile;

        protected int SectorSize
        {
            get => 
                this._sectorSize;
            set
            {
                this._sectorSize = value;
            }
        }
    }
}

